/****************************************************************************/
/* This file is part of FreeFem++.                                          */
/*                                                                          */
/* FreeFem++ is free software: you can redistribute it and/or modify        */
/* it under the terms of the GNU Lesser General Public License as           */
/* published by the Free Software Foundation, either version 3 of           */
/* the License, or (at your option) any later version.                      */
/*                                                                          */
/* FreeFem++ is distributed in the hope that it will be useful,             */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/* GNU Lesser General Public License for more details.                      */
/*                                                                          */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with FreeFem++. If not, see <http://www.gnu.org/licenses/>.        */
/****************************************************************************/
/* SUMMARY : ... */
/* LICENSE : LGPLv3 */
/* ORG     : LJLL Universite Pierre et Marie Curie, Paris, FRANCE */
/* AUTHORS : Pascal Frey */
/* E-MAIL  : pascal.frey@sorbonne-universite.fr
 */

#include <stdarg.h>
#include "medit.h"
#include "extern.h"
#include "sproto.h"

#define Width 630
#define Height 250
#define MAX_MAT 32

extern int refmat, reftype;

/* globals */
pScene main_scene;
GLint matwin = 0, m_subwin;
GLint selection;
static float colors[MAX_MAT + 1][3] = {
	{0.1, 0.4, 0.9},/* blue */
	{1.0, 0.0, 0.0},/* red */
	{0.0, 1.0, 0.0},/* green */
	{1.0, 1.0, 0.0},/* yellow */
	{0.0, 1.0, 1.0},/* cyan */
	{1.0, 0.5, 0.0},/* orange */
	{0.5, 0.0, 1.0},/* violet */
	{0.0, 0.0, 0.4},/* dark blue */
	{0.0, 0.4, 0.0},/* dark green */
	{0.4, 0.0, 0.0},/* dark red */
	{1.0, 1.0, 0.5},
	{1.0, 0.5, 1.0},
	{1.0, 0.5, 0.5},
	{1.0, 0.5, 0.0},/* orange */
	{1.0, 0.0, 1.0},
	{1.0, 0.0, 0.5},
	{0.5, 1.0, 1.0},
	{0.5, 1.0, 0.5},
	{0.5, 1.0, 0.0},
	{0.5, 0.5, 1.0},
	{0.5, 0.5, 0.5},
	{0.5, 0.5, 0.0},
	{0.5, 0.0, 0.5},
	{0.5, 0.0, 0.0},
	{0.0, 1.0, 0.5},
	{0.0, 0.5, 1.0},
	{0.0, 0.5, 0.5},
	{0.0, 0.5, 0.0},
	{0.0, 0.0, 0.5},
	{0.4, 0.4, 0.0},/* dark yellow */
	{0.0, 0.4, 0.4},/* dark cyan */
	{0.3, 0.7, 0.9},/* default blue */
	{0.3, 0.7, 0.9}	/* default blue */
};
cell ambient[4] = {
	{1, 120, 60, 0.0, 1.0, 0.0, 0.01,
	 "Specifies R coordinate of ambient vector.", "%.2f"},
	{2, 180, 60, 0.0, 1.0, 0.0, 0.01,
	 "Specifies G coordinate of ambient vector.", "%.2f"},
	{3, 240, 60, 0.0, 1.0, 0.0, 0.01,
	 "Specifies B coordinate of ambient vector.", "%.2f"},
	{4, 300, 60, 0.0, 1.0, 0.0, 0.01,
	 "Specifies A coordinate of ambient vector.", "%.2f"},
};
cell diffuse[4] = {
	{5, 120, 90, 0.0, 1.0, 0.0, 0.01,
	 "Specifies R coordinate of diffuse vector.", "%.2f"},
	{6, 180, 90, 0.0, 1.0, 0.0, 0.01,
	 "Specifies G coordinate of diffuse vector.", "%.2f"},
	{7, 240, 90, 0.0, 1.0, 0.0, 0.01,
	 "Specifies B coordinate of diffuse vector.", "%.2f"},
	{8, 300, 90, 0.0, 1.0, 0.0, 0.01,
	 "Specifies A coordinate of diffuse vector.", "%.2f"},
};
cell specular[4] = {
	{9, 120, 120, 0.0, 1.0, 0.0, 0.01,
	 "Specifies R coordinate of specular vector.", "%.2f"},
	{10, 180, 120, 0.0, 1.0, 0.0, 0.01,
	 "Specifies G coordinate of specular vector.", "%.2f"},
	{11, 240, 120, 0.0, 1.0, 0.0, 0.01,
	 "Specifies B coordinate of specular vector.", "%.2f"},
	{12, 300, 120, 0.0, 1.0, 0.0, 0.01,
	 "Specifies A coordinate of specular vector.", "%.2f"},
};
cell emission[4] = {
	{13, 120, 150, 0.0, 1.0, 0.0, 0.01,
	 "Specifies R coordinate of emission vector.", "%.2f"},
	{14, 180, 150, 0.0, 1.0, 0.0, 0.01,
	 "Specifies G coordinate of emission vector.", "%.2f"},
	{15, 240, 150, 0.0, 1.0, 0.0, 0.01,
	 "Specifies B coordinate of emission vector.", "%.2f"},
	{16, 300, 150, 0.0, 1.0, 0.0, 0.01,
	 "Specifies A coordinate of emission vector.", "%.2f"},
};
cell shininess[1] = {
	{17, 120, 180, 3.0, 128.0, 0.0, 0.5,
	 "Specifies value of shininess.", "%.2f"},
};

void matInit (pScene sc) {
	pMaterial pm;
	int m;

	/* default */
	if (!sc->material) {
		sc->material = (pMaterial)M_calloc(2 + sc->par.nbmat, sizeof(Material), "matinit");
		assert(sc->material);
		sc->matsort = (int *)M_calloc(2 + sc->par.nbmat, sizeof(int), "matinit");
		assert(sc->matsort);
	}

	/* store color in table */
	for (m = 0; m <= sc->par.nbmat; m++) {
		int mm;

		pm = &sc->material[m];
		/* diffuse : primary color */
		mm = m % MAX_MAT;
		memcpy(pm->dif, colors[mm], 3 * sizeof(float));
		pm->dif[3] = 1.0;
		/* ambient : grey level */
		pm->amb[0] = pm->amb[1] = pm->amb[2] = 0.2;
		pm->amb[3] = 1.0;
		/* emission: null (pas un neon!) */
		pm->emi[0] = pm->emi[1] = pm->emi[2] = 0.0;
		pm->emi[3] = 1.0;
		/* specular: soleil blanc */
		pm->spe[0] = pm->spe[1] = pm->spe[2] = 0.4;
		pm->spe[3] = 1.0;
		/* shininess: etalement des reflections spec. */
		pm->shininess = 80.0;
		if (m != DEFAULT_MAT)
			sprintf(pm->name, "%s%.2d", "MAT", m);
		else
			strcpy(pm->name, "DEFAULT_MAT");

		pm->flag = 0;
		if (!pm->ref) pm->ref = m;

		pm->sort = m;
	}
}

void matSort (pScene sc) {
	pMaterial pm, pm1;
	int m, mm, transp;

	/* sort materials */
	if (!quiet) fprintf(stdout, "   Sorting %d materials\n", sc->par.nbmat);

	transp = 0;

	for (m = 0; m < sc->par.nbmat; m++) {
		pm = &sc->material[m];
		pm->sort = m;
		sc->matsort[m] = m;
		if (pm->dif[3] < 0.999) transp++;
	}

	if (!transp) return;

	/* sorting */
	if (ddebug) fprintf(stdout, "   %d translucent\n", transp);

	mm = sc->par.nbmat - 1;

	for (m = 0; m < sc->par.nbmat - transp - 1; m++) {
		pm = &sc->material[m];
		if (pm->dif[3] < 0.995) {
			pm1 = &sc->material[mm];
			sc->matsort[mm] = m;
			sc->matsort[m] = mm;
			pm->sort = mm;
			pm1->sort = m;
			mm--;
		}
	}
}

int matRef (pScene sc, int ref) {
	int m;

	if (!ref) return (ref);

	for (m = 1; m < sc->par.nbmat; m++) {
		pMaterial pm;
		
		pm = &sc->material[m];
		if (pm->ref == ref) return (m);
	}

	if (sc->par.nbmat < 2) return (0);

	m = 1 + (ref - 1) % (sc->par.nbmat - 1);
	return (m);
}

/* reshape material window */
void matReshape (int width, int height) {
	glutSetWindow(matwin);
	glViewport(0, 0, Width, Height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0, Width, Height, 0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glutReshapeWindow(Width, Height);
}

static void cellInit (int mat) {
	pMaterial pm;
	int i;

	pm = &main_scene->material[mat];

	for (i = 0; i < 4; i++) {
		ambient[i].value = pm->amb[i];
		diffuse[i].value = pm->dif[i];
		specular[i].value = pm->spe[i];
		emission[i].value = pm->emi[i];
	}

	shininess[0].value = pm->shininess;
	glutPostRedisplay();
}

static int cellCopy (int mat) {
	pMaterial pm;
	int i, dosort;

	pm = &main_scene->material[mat];
	dosort = pm->dif[3] != diffuse[3].value;

	for (i = 0; i < 4; i++) {
		pm->amb[i] = ambient[i].value;
		pm->dif[i] = diffuse[i].value;
		pm->spe[i] = specular[i].value;
		pm->emi[i] = emission[i].value;
	}

	pm->shininess = shininess[0].value;
	return (dosort);
}

static void cellDraw (cell *cel) {
	if (selection == cel->id) {
		glColor3ub(255, 255, 0);
		drwstr(10, 240, cel->info);
		glColor3ub(255, 0, 0);
	} else {
		glColor3ub(0, 255, 128);
	}

	drwstr(cel->x, cel->y, cel->format, cel->value);
}

static int cellHit (cell *cel, int x, int y) {
	if (x > cel->x && x < cel->x + 60 &&
	    y > cel->y - 30 && y < cel->y + 10)
		return cel->id;
	else
		return 0;
}

static void cellUpdate (cell *cel, int update) {
	if (selection != cel->id)
		return;

	cel->value += update * cel->step;

	if (cel->value < cel->min)
		cel->value = cel->min;
	else if (cel->value > cel->max)
		cel->value = cel->max;
}

void matsubReshape (int x, int y) {
	GLfloat sunpos[4] = {5., 2., 10.0, 0.};

	glutSetWindow(m_subwin);
	glViewport(0, 0, 200, 200);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(50.0, 1.0, 0.01f, 100.0f);

	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
	glLightfv(GL_LIGHT0, GL_POSITION, sunpos);
	glEnable(GL_LIGHT0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, -5.0);
	glutSetWindow(matwin);
}

void matsubDisplay () {
	GLfloat amb[4], dif[4], emi[4], spe[4];
	int i, transp;

	glutSetWindow(m_subwin);
	glDisable(GL_COLOR_MATERIAL);
	glDisable(GL_LIGHTING);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glBegin(GL_QUADS);
	glColor3f(0.8, 0., 0.);
	glVertex3f(-2.0, -1.5, -2.0);
	glVertex3f(0.0, -1.5, -2.0);
	glVertex3f(0.0, -1.5, 5.0);
	glVertex3f(-2.0, -1.5, 5.0);

	glColor3f(0., 0.8, 0.);
	glVertex3f(0.0, -1.5, -2.0);
	glVertex3f(2.0, -1.5, -2.0);
	glVertex3f(2.0, -1.5, 5.0);
	glVertex3f(0.0, -1.5, 5.0);

	glColor3f(0., 0., 0.8);
	glVertex3f(0.0, -1.5, -5.0);
	glVertex3f(2.0, -1.5, -5.0);
	glVertex3f(2.0, -1.5, -2.0);
	glVertex3f(0.0, -1.5, -2.0);

	glColor3f(0.4, 0.4, 0.4);
	glVertex3f(-2.0, -1.5, -5.0);
	glVertex3f(0.0, -1.5, -5.0);
	glVertex3f(0.0, -1.5, -2.0);
	glVertex3f(-2.0, -1.5, -2.0);
	glEnd();

	glEnable(GL_LIGHTING);
	transp = ambient[3].value < 0.999 ||
	         diffuse[3].value < 0.999 || specular[3].value < 0.999;
	if (transp) {
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDepthMask(GL_FALSE);
	}

	for (i = 0; i < 4; i++) {
		amb[i] = ambient[i].value;
		dif[i] = diffuse[i].value;
		spe[i] = specular[i].value;
		emi[i] = emission[i].value;
	}

	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, dif);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, amb);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, spe);
	glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emi);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &shininess[0].value);

	glutSolidSphere(1.5, 20, 10);
	if (transp) {
		glDepthMask(GL_TRUE);
		glDisable(GL_BLEND);
	}

	glutSwapBuffers();
	glutSetWindow(matwin);
}

/* display mat properties */
void matDisplay () {
	pMaterial pm;

	glutSetWindow(matwin);
	glClearColor(0.1, 0.1, 0.1, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	pm = &main_scene->material[refmat];

	glColor3ub(255, 255, 0);
	setFont("helvetica", 18);
	drwstr(10, 20, pm->name);

	glColor3ub(255, 255, 255);
	setFont("helvetica", 18);
	drwstr(30, ambient[0].y, "Ambient(");
	drwstr(170, ambient[0].y, ",");
	drwstr(230, ambient[1].y, ",");
	drwstr(290, ambient[2].y, ",");
	drwstr(350, ambient[3].y, ");");

	cellDraw(&ambient[0]);
	cellDraw(&ambient[1]);
	cellDraw(&ambient[2]);
	cellDraw(&ambient[3]);

	glColor3ub(255, 255, 255);
	drwstr(40, diffuse[0].y, "Diffuse(");
	drwstr(170, diffuse[0].y, ",");
	drwstr(230, diffuse[1].y, ",");
	drwstr(290, diffuse[2].y, ",");
	drwstr(350, diffuse[3].y, ");");

	cellDraw(&diffuse[0]);
	cellDraw(&diffuse[1]);
	cellDraw(&diffuse[2]);
	cellDraw(&diffuse[3]);

	glColor3ub(255, 255, 255);
	drwstr(25, specular[0].y, "Specular(");
	drwstr(170, specular[0].y, ",");
	drwstr(230, specular[1].y, ",");
	drwstr(290, specular[2].y, ",");
	drwstr(350, specular[3].y, ");");

	cellDraw(&specular[0]);
	cellDraw(&specular[1]);
	cellDraw(&specular[2]);
	cellDraw(&specular[3]);

	glColor3ub(255, 255, 255);
	drwstr(25, emission[0].y, "Emission(");
	drwstr(170, emission[0].y, ",");
	drwstr(230, emission[1].y, ",");
	drwstr(290, emission[2].y, ",");
	drwstr(350, emission[3].y, ");");

	cellDraw(&emission[0]);
	cellDraw(&emission[1]);
	cellDraw(&emission[2]);
	cellDraw(&emission[3]);

	glColor3ub(255, 255, 255);
	drwstr(20, shininess[0].y, "Shininess(");
	drwstr(350, shininess[0].y, ");");
	cellDraw(&shininess[0]);

	if (!selection) {
		glColor3ub(255, 255, 0);
		setFont("helvetica", 18);
		drwstr(10, 240, "Click and move the mouse to modify values ('A' to apply).");
	}

	glutSwapBuffers();
}

static int old_y = 0;

void matMouse (int button, int state, int x, int y) {
	glutSetWindow(matwin);
	selection = 0;
	if (button != GLUT_LEFT_BUTTON) return;

	if (state == GLUT_DOWN) {
		/* mouse should only hit _one_ of the cells, so adding up all
		 * the hits just propagates a single hit. */
		selection += cellHit(&ambient[0], x, y);
		selection += cellHit(&ambient[1], x, y);
		selection += cellHit(&ambient[2], x, y);
		selection += cellHit(&ambient[3], x, y);
		selection += cellHit(&diffuse[0], x, y);
		selection += cellHit(&diffuse[1], x, y);
		selection += cellHit(&diffuse[2], x, y);
		selection += cellHit(&diffuse[3], x, y);
		selection += cellHit(&specular[0], x, y);
		selection += cellHit(&specular[1], x, y);
		selection += cellHit(&specular[2], x, y);
		selection += cellHit(&specular[3], x, y);
		selection += cellHit(&emission[0], x, y);
		selection += cellHit(&emission[1], x, y);
		selection += cellHit(&emission[2], x, y);
		selection += cellHit(&emission[3], x, y);
		selection += cellHit(&shininess[0], x, y);
	} else {
		/* update material */
		glutSetWindow(m_subwin);
	}

	old_y = y;
	glutPostRedisplay();
}

static void matMotion (int x, int y) {
	cellUpdate(&ambient[0], old_y - y);
	cellUpdate(&ambient[1], old_y - y);
	cellUpdate(&ambient[2], old_y - y);
	cellUpdate(&ambient[3], old_y - y);

	cellUpdate(&diffuse[0], old_y - y);
	cellUpdate(&diffuse[1], old_y - y);
	cellUpdate(&diffuse[2], old_y - y);
	cellUpdate(&diffuse[3], old_y - y);

	cellUpdate(&specular[0], old_y - y);
	cellUpdate(&specular[1], old_y - y);
	cellUpdate(&specular[2], old_y - y);
	cellUpdate(&specular[3], old_y - y);

	cellUpdate(&emission[0], old_y - y);
	cellUpdate(&emission[1], old_y - y);
	cellUpdate(&emission[2], old_y - y);
	cellUpdate(&emission[3], old_y - y);

	cellUpdate(&shininess[0], old_y - y);
	old_y = y;
	glutPostRedisplay();

	glutSetWindow(m_subwin);
	glutPostRedisplay();
}

void matKeyboard (unsigned char key, int x, int y) {
	if (isdigit(key)) return;

	glutSetWindow(matwin);

	switch (key) {
	case 'a':
	case 'A':
		if (cellCopy(refmat))
			matSort(main_scene);

		matwin = 0;
		glutHideWindow();
		/*glutPostRedisplay();*/
		glutSetWindow(main_scene->idwin);
		doLists(main_scene, cv.mesh[main_scene->idmesh]);
		glutPostRedisplay();
		break;
	case 'q':
	case 27:
		matwin = 0;
		glutHideWindow();
		glutPostRedisplay();
		break;
	}
}

void matEdit (pScene sc) {
	/* init window */
	if (matwin || refmat < 0) return;

	matwin = m_subwin = 0;

	/* create 2d window */
	glutInitWindowSize(Width, Height);
	matwin = glutCreateWindow("Material Properties");
	if (!matwin) return;

	main_scene = sc;

	/* set callbacks */
	glutSetIconTitle("MatProp");
	glutReshapeFunc(matReshape);
	glutDisplayFunc(matDisplay);
	glutKeyboardFunc(matKeyboard);
	glutMouseFunc(matMouse);
	glutMotionFunc(matMotion);

	/* create 3d sub-window */
	glutSetWindow(matwin);
	m_subwin = glutCreateSubWindow(matwin, 400, 20, 200, 200);
	if (ddebug) printf("window %d\n", (int)matwin);

	glutDisplayFunc(matsubDisplay);
	glutReshapeFunc(matsubReshape);

	glutSetWindow(matwin);
	cellInit(refmat);
}

#ifdef __cplusplus
}
#endif
